1. Project Clover database Thu Jul 25 2024 07:51:22 UTC
  2. Package org.joda.time

File DateTimeZone.java

 

Coverage histogram

../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

170
347
49
3
1,398
620
158
0.46
7.08
16.33
3.22

Classes

Class Line # Actions
DateTimeZone 102 302 0% 148 39
0.9236790592.4%
DateTimeZone.Stub 1296 4 0% 4 0
1.0100%
DateTimeZone.LazyInit 1327 41 0% 6 4
0.914893691.5%
 

Contributing tests

This file is covered by 2839 tests. .

Source view

1    /*
2    * Copyright 2001-2014 Stephen Colebourne
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10    * Unless required by applicable law or agreed to in writing, software
11    * distributed under the License is distributed on an "AS IS" BASIS,
12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    * See the License for the specific language governing permissions and
14    * limitations under the License.
15    */
16    package org.joda.time;
17   
18    import java.io.File;
19    import java.io.IOException;
20    import java.io.ObjectInputStream;
21    import java.io.ObjectOutputStream;
22    import java.io.ObjectStreamException;
23    import java.io.Serializable;
24    import java.util.Collections;
25    import java.util.HashMap;
26    import java.util.Locale;
27    import java.util.Map;
28    import java.util.Set;
29    import java.util.TimeZone;
30    import java.util.concurrent.atomic.AtomicReference;
31   
32    import org.joda.convert.FromString;
33    import org.joda.convert.ToString;
34    import org.joda.time.chrono.BaseChronology;
35    import org.joda.time.field.FieldUtils;
36    import org.joda.time.format.DateTimeFormatter;
37    import org.joda.time.format.DateTimeFormatterBuilder;
38    import org.joda.time.format.FormatUtils;
39    import org.joda.time.tz.DefaultNameProvider;
40    import org.joda.time.tz.FixedDateTimeZone;
41    import org.joda.time.tz.NameProvider;
42    import org.joda.time.tz.Provider;
43    import org.joda.time.tz.UTCProvider;
44    import org.joda.time.tz.ZoneInfoProvider;
45   
46    /**
47    * DateTimeZone represents a time zone.
48    * <p>
49    * A time zone is a system of rules to convert time from one geographic
50    * location to another. For example, Paris, France is one hour ahead of
51    * London, England. Thus when it is 10:00 in London, it is 11:00 in Paris.
52    * <p>
53    * All time zone rules are expressed, for historical reasons, relative to
54    * Greenwich, London. Local time in Greenwich is referred to as Greenwich Mean
55    * Time (GMT). This is similar, but not precisely identical, to Universal
56    * Coordinated Time, or UTC. This library only uses the term UTC.
57    * <p>
58    * Using this system, America/Los_Angeles is expressed as UTC-08:00, or UTC-07:00
59    * in the summer. The offset -08:00 indicates that America/Los_Angeles time is
60    * obtained from UTC by adding -08:00, that is, by subtracting 8 hours.
61    * <p>
62    * The offset differs in the summer because of daylight saving time, or DST.
63    * The following definitions of time are generally used:
64    * <ul>
65    * <li>UTC - The reference time.
66    * <li>Standard Time - The local time without a daylight saving time offset.
67    * For example, in Paris, standard time is UTC+01:00.
68    * <li>Daylight Saving Time - The local time with a daylight saving time
69    * offset. This offset is typically one hour, but not always. It is typically
70    * used in most countries away from the equator. In Paris, daylight saving
71    * time is UTC+02:00.
72    * <li>Wall Time - This is what a local clock on the wall reads. This will be
73    * either Standard Time or Daylight Saving Time depending on the time of year
74    * and whether the location uses Daylight Saving Time.
75    * </ul>
76    * <p>
77    * Unlike the Java TimeZone class, DateTimeZone is immutable. It also only
78    * supports long format time zone ids. Thus EST and ECT are not accepted.
79    * However, the factory that accepts a TimeZone will attempt to convert from
80    * the old short id to a suitable long id.
81    * <p>
82    * There are four approaches to loading time-zone data, which are tried in this order:
83    * <ol>
84    * <li>load the specific {@link Provider} specified by the system property
85    * {@code org.joda.time.DateTimeZone.Provider}.
86    * <li>load {@link ZoneInfoProvider} using the data in the filing system folder
87    * pointed to by system property {@code org.joda.time.DateTimeZone.Folder}.
88    * <li>load {@link ZoneInfoProvider} using the data in the classpath location
89    * {@code org/joda/time/tz/data}.
90    * <li>load {@link UTCProvider}
91    * </ol>
92    * <p>
93    * Unless you override the standard behaviour, the default if the third approach.
94    * <p>
95    * DateTimeZone is thread-safe and immutable, and all subclasses must be as
96    * well.
97    *
98    * @author Brian S O'Neill
99    * @author Stephen Colebourne
100    * @since 1.0
101    */
 
102    public abstract class DateTimeZone implements Serializable {
103   
104    /** Serialization version. */
105    private static final long serialVersionUID = 5546345482340108586L;
106   
107    /** The time zone for Universal Coordinated Time */
108    public static final DateTimeZone UTC = UTCDateTimeZone.INSTANCE;
109    /** Maximum offset. */
110    private static final int MAX_MILLIS = (86400 * 1000) - 1;
111   
112    /**
113    * The instance that is providing time zones.
114    * This is lazily initialized to reduce risks of race conditions at startup.
115    */
116    private static final AtomicReference<Provider> cProvider =
117    new AtomicReference<Provider>();
118    /**
119    * The instance that is providing time zone names.
120    * This is lazily initialized to reduce risks of race conditions at startup.
121    */
122    private static final AtomicReference<NameProvider> cNameProvider =
123    new AtomicReference<NameProvider>();
124    /**
125    * The default time zone.
126    * This is lazily initialized to reduce risks of race conditions at startup.
127    */
128    private static final AtomicReference<DateTimeZone> cDefault =
129    new AtomicReference<DateTimeZone>();
130    /**
131    * The default TZ data path
132    * This is the default classpath location containing the compiled data files.
133    */
134    public static final String DEFAULT_TZ_DATA_PATH = "org/joda/time/tz/data";
135   
136    //-----------------------------------------------------------------------
137    /**
138    * Gets the default time zone.
139    * <p>
140    * The default time zone is derived from the system property {@code user.timezone}.
141    * If that is {@code null} or is not a valid identifier, then the value of the
142    * JDK {@code TimeZone} default is converted. If that fails, {@code UTC} is used.
143    * <p>
144    * NOTE: If the {@code java.util.TimeZone} default is updated <i>after</i> calling this
145    * method, then the change will not be picked up here.
146    *
147    * @return the default datetime zone object
148    */
 
149  22047 toggle public static DateTimeZone getDefault() {
150  22047 DateTimeZone zone = cDefault.get();
151  22047 if (zone == null) {
152  2 try {
153  2 try {
154  2 String id = System.getProperty("user.timezone");
155  2 if (id != null) { // null check avoids stack overflow
156  1 zone = forID(id);
157    }
158    } catch (RuntimeException ex) {
159    // ignored
160    }
161  2 if (zone == null) {
162  2 zone = forTimeZone(TimeZone.getDefault());
163    }
164    } catch (IllegalArgumentException ex) {
165    // ignored
166    }
167  2 if (zone == null) {
168  1 zone = UTC;
169    }
170  2 if (!cDefault.compareAndSet(null, zone)) {
171  0 zone = cDefault.get();
172    }
173    }
174  22047 return zone;
175    }
176   
177    /**
178    * Sets the default time zone.
179    * <p>
180    * NOTE: Calling this method does <i>not</i> set the {@code java.util.TimeZone} default.
181    *
182    * @param zone the default datetime zone object, must not be null
183    * @throws IllegalArgumentException if the zone is null
184    * @throws SecurityException if the application has insufficient security rights
185    */
 
186  6285 toggle public static void setDefault(DateTimeZone zone) throws SecurityException {
187  6285 SecurityManager sm = System.getSecurityManager();
188  6285 if (sm != null) {
189  0 sm.checkPermission(new JodaTimePermission("DateTimeZone.setDefault"));
190    }
191  6285 if (zone == null) {
192  1 throw new IllegalArgumentException("The datetime zone must not be null");
193    }
194  6284 cDefault.set(zone);
195    }
196   
197    //-----------------------------------------------------------------------
198    /**
199    * Gets a time zone instance for the specified time zone id.
200    * <p>
201    * The time zone id may be one of those returned by getAvailableIDs.
202    * Short ids, as accepted by {@link java.util.TimeZone}, are not accepted.
203    * All IDs must be specified in the long format.
204    * The exception is UTC, which is an acceptable id.
205    * <p>
206    * Alternatively a locale independent, fixed offset, datetime zone can
207    * be specified. The form <code>[+-]hh:mm</code> can be used.
208    *
209    * @param id the ID of the datetime zone, null means default
210    * @return the DateTimeZone object for the ID
211    * @throws IllegalArgumentException if the ID is not recognised
212    */
 
213  531 toggle @FromString
214    public static DateTimeZone forID(String id) {
215  531 if (id == null) {
216  1 return getDefault();
217    }
218  530 if (id.equals("UTC")) {
219  14 return DateTimeZone.UTC;
220    }
221  516 DateTimeZone zone = getProvider().getZone(id);
222  516 if (zone != null) {
223  478 return zone;
224    }
225  38 if (id.startsWith("+") || id.startsWith("-")) {
226  34 int offset = parseOffset(id);
227  32 if (offset == 0L) {
228  3 return DateTimeZone.UTC;
229    } else {
230  29 id = printOffset(offset);
231  29 return fixedOffsetZone(id, offset);
232    }
233    }
234  4 throw new IllegalArgumentException("The datetime zone id '" + id + "' is not recognised");
235    }
236   
237    /**
238    * Gets a time zone instance for the specified offset to UTC in hours.
239    * This method assumes standard length hours.
240    * <p>
241    * This factory is a convenient way of constructing zones with a fixed offset.
242    *
243    * @param hoursOffset the offset in hours from UTC, from -23 to +23
244    * @return the DateTimeZone object for the offset
245    * @throws IllegalArgumentException if the offset is too large or too small
246    */
 
247  75 toggle public static DateTimeZone forOffsetHours(int hoursOffset) throws IllegalArgumentException {
248  75 return forOffsetHoursMinutes(hoursOffset, 0);
249    }
250   
251    /**
252    * Gets a time zone instance for the specified offset to UTC in hours and minutes.
253    * This method assumes 60 minutes in an hour, and standard length minutes.
254    * <p>
255    * This factory is a convenient way of constructing zones with a fixed offset.
256    * The hours value must be in the range -23 to +23.
257    * The minutes value must be in the range -59 to +59.
258    * The following combinations of sign for the hour and minute are possible:
259    * <pre>
260    * Hour Minute Example Result
261    *
262    * +ve +ve (2, 15) +02:15
263    * +ve zero (2, 0) +02:00
264    * +ve -ve (2, -15) IllegalArgumentException
265    *
266    * zero +ve (0, 15) +00:15
267    * zero zero (0, 0) +00:00
268    * zero -ve (0, -15) -00:15
269    *
270    * -ve +ve (-2, 15) -02:15
271    * -ve zero (-2, 0) -02:00
272    * -ve -ve (-2, -15) -02:15
273    * </pre>
274    * Note that in versions before 2.3, the minutes had to be zero or positive.
275    *
276    * @param hoursOffset the offset in hours from UTC, from -23 to +23
277    * @param minutesOffset the offset in minutes from UTC, from -59 to +59
278    * @return the DateTimeZone object for the offset
279    * @throws IllegalArgumentException if any value is out of range, the minutes are negative
280    * when the hours are positive, or the resulting offset exceeds +/- 23:59:59.000
281    */
 
282  92 toggle public static DateTimeZone forOffsetHoursMinutes(int hoursOffset, int minutesOffset) throws IllegalArgumentException {
283  92 if (hoursOffset == 0 && minutesOffset == 0) {
284  4 return DateTimeZone.UTC;
285    }
286  88 if (hoursOffset < -23 || hoursOffset > 23) {
287  3 throw new IllegalArgumentException("Hours out of range: " + hoursOffset);
288    }
289  85 if (minutesOffset < -59 || minutesOffset > 59) {
290  2 throw new IllegalArgumentException("Minutes out of range: " + minutesOffset);
291    }
292  83 if (hoursOffset > 0 && minutesOffset < 0) {
293  1 throw new IllegalArgumentException("Positive hours must not have negative minutes: " + minutesOffset);
294    }
295  82 int offset = 0;
296  82 try {
297  82 int hoursInMinutes = hoursOffset * 60;
298  82 if (hoursInMinutes < 0) {
299  32 minutesOffset = hoursInMinutes - Math.abs(minutesOffset);
300    } else {
301  50 minutesOffset = hoursInMinutes + minutesOffset;
302    }
303  82 offset = FieldUtils.safeMultiply(minutesOffset, DateTimeConstants.MILLIS_PER_MINUTE);
304    } catch (ArithmeticException ex) {
305  0 throw new IllegalArgumentException("Offset is too large");
306    }
307  82 return forOffsetMillis(offset);
308    }
309   
310    /**
311    * Gets a time zone instance for the specified offset to UTC in milliseconds.
312    *
313    * @param millisOffset the offset in millis from UTC, from -23:59:59.999 to +23:59:59.999
314    * @return the DateTimeZone object for the offset
315    */
 
316  175 toggle public static DateTimeZone forOffsetMillis(int millisOffset) {
317  175 if (millisOffset < -MAX_MILLIS || millisOffset > MAX_MILLIS) {
318  0 throw new IllegalArgumentException("Millis out of range: " + millisOffset);
319    }
320  175 String id = printOffset(millisOffset);
321  175 return fixedOffsetZone(id, millisOffset);
322    }
323   
324    /**
325    * Gets a time zone instance for a JDK TimeZone.
326    * <p>
327    * DateTimeZone only accepts a subset of the IDs from TimeZone. The
328    * excluded IDs are the short three letter form (except UTC). This
329    * method will attempt to convert between time zones created using the
330    * short IDs and the full version.
331    * <p>
332    * This method is not designed to parse time zones with rules created by
333    * applications using <code>SimpleTimeZone</code> directly.
334    *
335    * @param zone the zone to convert, null means default
336    * @return the DateTimeZone object for the zone
337    * @throws IllegalArgumentException if the zone is not recognised
338    */
 
339  71 toggle public static DateTimeZone forTimeZone(TimeZone zone) {
340  71 if (zone == null) {
341  1 return getDefault();
342    }
343  70 final String id = zone.getID();
344  70 if (id == null) {
345  1 throw new IllegalArgumentException("The TimeZone id must not be null");
346    }
347  69 if (id.equals("UTC")) {
348  1 return DateTimeZone.UTC;
349    }
350   
351    // Convert from old alias before consulting provider since they may differ.
352  68 DateTimeZone dtz = null;
353  68 String convId = getConvertedId(id);
354  68 Provider provider = getProvider();
355  68 if (convId != null) {
356  35 dtz = provider.getZone(convId);
357    }
358  68 if (dtz == null) {
359  33 dtz = provider.getZone(id);
360    }
361  68 if (dtz != null) {
362  57 return dtz;
363    }
364   
365    // Support GMT+/-hh:mm formats
366  11 if (convId == null) {
367  11 convId = id;
368  11 if (convId.startsWith("GMT+") || convId.startsWith("GMT-")) {
369  9 convId = convId.substring(3);
370  9 if (convId.length() > 2) {
371  9 char firstDigit = convId.charAt(1);
372  9 if (firstDigit > '9' && Character.isDigit(firstDigit)) {
373  1 convId = convertToAsciiNumber(convId);
374    }
375    }
376  9 int offset = parseOffset(convId);
377  9 if (offset == 0L) {
378  3 return DateTimeZone.UTC;
379    } else {
380  6 convId = printOffset(offset);
381  6 return fixedOffsetZone(convId, offset);
382    }
383    }
384    }
385  2 throw new IllegalArgumentException("The datetime zone id '" + id + "' is not recognised");
386    }
387   
 
388  1 toggle private static String convertToAsciiNumber(String convId) {
389  1 StringBuilder buf = new StringBuilder(convId);
390  7 for (int i = 0; i < buf.length(); i++) {
391  6 char ch = buf.charAt(i);
392  6 int digit = Character.digit(ch, 10);
393  6 if (digit >= 0) {
394  4 buf.setCharAt(i, (char) ('0' + digit));
395    }
396    }
397  1 return buf.toString();
398    }
399   
400    //-----------------------------------------------------------------------
401    /**
402    * Gets the zone using a fixed offset amount.
403    *
404    * @param id the zone id
405    * @param offset the offset in millis
406    * @return the zone
407    */
 
408  210 toggle private static DateTimeZone fixedOffsetZone(String id, int offset) {
409  210 if (offset == 0) {
410  17 return DateTimeZone.UTC;
411    }
412  193 return new FixedDateTimeZone(id, null, offset, offset);
413    }
414   
415    /**
416    * Gets all the available IDs supported.
417    *
418    * @return an unmodifiable Set of String IDs
419    */
 
420  10 toggle public static Set<String> getAvailableIDs() {
421  10 return getProvider().getAvailableIDs();
422    }
423   
424    //-----------------------------------------------------------------------
425    /**
426    * Gets the zone provider factory.
427    * <p>
428    * The zone provider is a pluggable instance factory that supplies the
429    * actual instances of DateTimeZone.
430    *
431    * @return the provider
432    */
 
433  604 toggle public static Provider getProvider() {
434  604 Provider provider = cProvider.get();
435  604 if (provider == null) {
436  2 provider = getDefaultProvider();
437  2 if (!cProvider.compareAndSet(null, provider)) {
438  0 provider = cProvider.get();
439    }
440    }
441  604 return provider;
442    }
443   
444    /**
445    * Sets the zone provider factory.
446    * <p>
447    * The zone provider is a pluggable instance factory that supplies the
448    * actual instances of DateTimeZone.
449    *
450    * @param provider provider to use, or null for default
451    * @throws SecurityException if you do not have the permission DateTimeZone.setProvider
452    * @throws IllegalArgumentException if the provider is invalid
453    */
 
454  15 toggle public static void setProvider(Provider provider) throws SecurityException {
455  15 SecurityManager sm = System.getSecurityManager();
456  15 if (sm != null) {
457  0 sm.checkPermission(new JodaTimePermission("DateTimeZone.setProvider"));
458    }
459  15 if (provider == null) {
460  10 provider = getDefaultProvider();
461    } else {
462  5 validateProvider(provider);
463    }
464  10 cProvider.set(provider);
465    }
466   
467    /**
468    * Sets the zone provider factory without performing the security check.
469    *
470    * @param provider provider to use, or null for default
471    * @return the provider
472    * @throws IllegalArgumentException if the provider is invalid
473    */
 
474  16 toggle private static Provider validateProvider(Provider provider) {
475  16 Set<String> ids = provider.getAvailableIDs();
476  16 if (ids == null || ids.size() == 0) {
477  2 throw new IllegalArgumentException("The provider doesn't have any available ids");
478    }
479  14 if (!ids.contains("UTC")) {
480  1 throw new IllegalArgumentException("The provider doesn't support UTC");
481    }
482  13 if (!UTC.equals(provider.getZone("UTC"))) {
483  1 throw new IllegalArgumentException("Invalid UTC zone provided");
484    }
485  12 return provider;
486    }
487   
488    /**
489    * Gets the default zone provider.
490    * <p>
491    * This tries four approaches to loading data:
492    * <ol>
493    * <li>loads the provider identifier by the system property
494    * <code>org.joda.time.DateTimeZone.Provider</code>.
495    * <li>load <code>ZoneInfoProvider</code> using the data in the filing system folder
496    * pointed to by system property <code>org.joda.time.DateTimeZone.Folder</code>.
497    * <li>loads <code>ZoneInfoProvider</code> using the data in the classpath location
498    * <code>org/joda/time/tz/data</code>.
499    * <li>loads <code>UTCProvider</code>.
500    * </ol>
501    * <p>
502    * Unless you override the standard behaviour, the default if the third approach.
503    *
504    * @return the default name provider
505    */
 
506  12 toggle private static Provider getDefaultProvider() {
507    // approach 1
508  12 try {
509  12 String providerClass = System.getProperty("org.joda.time.DateTimeZone.Provider");
510  12 if (providerClass != null) {
511  3 try {
512  3 Provider provider = (Provider) Class.forName(providerClass).newInstance();
513  2 return validateProvider(provider);
514    } catch (Exception ex) {
515  1 throw new RuntimeException(ex);
516    }
517    }
518    } catch (SecurityException ex) {
519    // ignored
520    }
521    // approach 2
522  9 try {
523  9 String dataFolder = System.getProperty("org.joda.time.DateTimeZone.Folder");
524  9 if (dataFolder != null) {
525  1 try {
526  1 Provider provider = new ZoneInfoProvider(new File(dataFolder));
527  1 return validateProvider(provider);
528    } catch (Exception ex) {
529  0 throw new RuntimeException(ex);
530    }
531    }
532    } catch (SecurityException ex) {
533    // ignored
534    }
535    // approach 3
536  8 try {
537  8 Provider provider = new ZoneInfoProvider(DEFAULT_TZ_DATA_PATH);
538  8 return validateProvider(provider);
539    } catch (Exception ex) {
540  0 ex.printStackTrace();
541    }
542    // approach 4
543  0 return new UTCProvider();
544    }
545   
546    //-----------------------------------------------------------------------
547    /**
548    * Gets the name provider factory.
549    * <p>
550    * The name provider is a pluggable instance factory that supplies the
551    * names of each DateTimeZone.
552    *
553    * @return the provider
554    */
 
555  91 toggle public static NameProvider getNameProvider() {
556  91 NameProvider nameProvider = cNameProvider.get();
557  91 if (nameProvider == null) {
558  1 nameProvider = getDefaultNameProvider();
559  1 if (!cNameProvider.compareAndSet(null, nameProvider)) {
560  0 nameProvider = cNameProvider.get();
561    }
562    }
563  91 return nameProvider;
564    }
565   
566    /**
567    * Sets the name provider factory.
568    * <p>
569    * The name provider is a pluggable instance factory that supplies the
570    * names of each DateTimeZone.
571    *
572    * @param nameProvider provider to use, or null for default
573    * @throws SecurityException if you do not have the permission DateTimeZone.setNameProvider
574    * @throws IllegalArgumentException if the provider is invalid
575    */
 
576  5 toggle public static void setNameProvider(NameProvider nameProvider) throws SecurityException {
577  5 SecurityManager sm = System.getSecurityManager();
578  5 if (sm != null) {
579  0 sm.checkPermission(new JodaTimePermission("DateTimeZone.setNameProvider"));
580    }
581  5 if (nameProvider == null) {
582  4 nameProvider = getDefaultNameProvider();
583    }
584  5 cNameProvider.set(nameProvider);
585    }
586   
587    /**
588    * Gets the default name provider.
589    * <p>
590    * Tries the system property <code>org.joda.time.DateTimeZone.NameProvider</code>.
591    * Then uses <code>DefaultNameProvider</code>.
592    *
593    * @return the default name provider
594    */
 
595  5 toggle private static NameProvider getDefaultNameProvider() {
596  5 NameProvider nameProvider = null;
597  5 try {
598  5 String providerClass = System.getProperty("org.joda.time.DateTimeZone.NameProvider");
599  5 if (providerClass != null) {
600  1 try {
601  1 nameProvider = (NameProvider) Class.forName(providerClass).newInstance();
602    } catch (Exception ex) {
603  0 throw new RuntimeException(ex);
604    }
605    }
606    } catch (SecurityException ex) {
607    // ignore
608    }
609   
610  5 if (nameProvider == null) {
611  4 nameProvider = new DefaultNameProvider();
612    }
613   
614  5 return nameProvider;
615    }
616   
617    //-----------------------------------------------------------------------
618    /**
619    * Converts an old style id to a new style id.
620    *
621    * @param id the old style id
622    * @return the new style id, null if not found
623    */
 
624  68 toggle private static String getConvertedId(String id) {
625  68 return LazyInit.CONVERSION_MAP.get(id);
626    }
627   
628    /**
629    * Parses an offset from the string.
630    *
631    * @param str the string
632    * @return the offset millis
633    */
 
634  43 toggle private static int parseOffset(String str) {
635  43 return -(int) LazyInit.OFFSET_FORMATTER.parseMillis(str);
636    }
637   
638    /**
639    * Formats a timezone offset string.
640    * <p>
641    * This method is kept separate from the formatting classes to speed and
642    * simplify startup and classloading.
643    *
644    * @param offset the offset in milliseconds
645    * @return the time zone string
646    */
 
647  212 toggle private static String printOffset(int offset) {
648  212 StringBuffer buf = new StringBuffer();
649  212 if (offset >= 0) {
650  158 buf.append('+');
651    } else {
652  54 buf.append('-');
653  54 offset = -offset;
654    }
655   
656  212 int hours = offset / DateTimeConstants.MILLIS_PER_HOUR;
657  212 FormatUtils.appendPaddedInteger(buf, hours, 2);
658  212 offset -= hours * (int) DateTimeConstants.MILLIS_PER_HOUR;
659   
660  212 int minutes = offset / DateTimeConstants.MILLIS_PER_MINUTE;
661  212 buf.append(':');
662  212 FormatUtils.appendPaddedInteger(buf, minutes, 2);
663  212 offset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
664  212 if (offset == 0) {
665  204 return buf.toString();
666    }
667   
668  8 int seconds = offset / DateTimeConstants.MILLIS_PER_SECOND;
669  8 buf.append(':');
670  8 FormatUtils.appendPaddedInteger(buf, seconds, 2);
671  8 offset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
672  8 if (offset == 0) {
673  2 return buf.toString();
674    }
675   
676  6 buf.append('.');
677  6 FormatUtils.appendPaddedInteger(buf, offset, 3);
678  6 return buf.toString();
679    }
680   
681    // Instance fields and methods
682    //--------------------------------------------------------------------
683   
684    private final String iID;
685   
686    /**
687    * Constructor.
688    *
689    * @param id the id to use
690    * @throws IllegalArgumentException if the id is null
691    */
 
692  3270 toggle protected DateTimeZone(String id) {
693  3270 if (id == null) {
694  1 throw new IllegalArgumentException("Id must not be null");
695    }
696  3269 iID = id;
697    }
698   
699    // Principal methods
700    //--------------------------------------------------------------------
701   
702    /**
703    * Gets the ID of this datetime zone.
704    *
705    * @return the ID of this datetime zone
706    */
 
707  122514 toggle @ToString
708    public final String getID() {
709  122514 return iID;
710    }
711   
712    /**
713    * Returns a non-localized name that is unique to this time zone. It can be
714    * combined with id to form a unique key for fetching localized names.
715    *
716    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the name for
717    * @return name key or null if id should be used for names
718    */
719    public abstract String getNameKey(long instant);
720   
721    /**
722    * Gets the short name of this datetime zone suitable for display using
723    * the default locale.
724    * <p>
725    * If the name is not available for the locale, then this method returns a
726    * string in the format <code>[+-]hh:mm</code>.
727    *
728    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the name for
729    * @return the human-readable short name in the default locale
730    */
 
731  3 toggle public final String getShortName(long instant) {
732  3 return getShortName(instant, null);
733    }
734   
735    /**
736    * Gets the short name of this datetime zone suitable for display using
737    * the specified locale.
738    * <p>
739    * If the name is not available for the locale, then this method returns a
740    * string in the format <code>[+-]hh:mm</code>.
741    *
742    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the name for
743    * @param locale the locale to get the name for
744    * @return the human-readable short name in the specified locale
745    */
 
746  38 toggle public String getShortName(long instant, Locale locale) {
747  38 if (locale == null) {
748  3 locale = Locale.getDefault();
749    }
750  38 String nameKey = getNameKey(instant);
751  38 if (nameKey == null) {
752  1 return iID;
753    }
754  37 String name;
755  37 NameProvider np = getNameProvider();
756  37 if (np instanceof DefaultNameProvider) {
757  36 name = ((DefaultNameProvider) np).getShortName(locale, iID, nameKey, isStandardOffset(instant));
758    } else {
759  1 name = np.getShortName(locale, iID, nameKey);
760    }
761  37 if (name != null) {
762  36 return name;
763    }
764  1 return printOffset(getOffset(instant));
765    }
766   
767    /**
768    * Gets the long name of this datetime zone suitable for display using
769    * the default locale.
770    * <p>
771    * If the name is not available for the locale, then this method returns a
772    * string in the format <code>[+-]hh:mm</code>.
773    *
774    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the name for
775    * @return the human-readable long name in the default locale
776    */
 
777  11 toggle public final String getName(long instant) {
778  11 return getName(instant, null);
779    }
780   
781    /**
782    * Gets the long name of this datetime zone suitable for display using
783    * the specified locale.
784    * <p>
785    * If the name is not available for the locale, then this method returns a
786    * string in the format <code>[+-]hh:mm</code>.
787    *
788    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the name for
789    * @param locale the locale to get the name for
790    * @return the human-readable long name in the specified locale
791    */
 
792  41 toggle public String getName(long instant, Locale locale) {
793  41 if (locale == null) {
794  11 locale = Locale.getDefault();
795    }
796  41 String nameKey = getNameKey(instant);
797  41 if (nameKey == null) {
798  1 return iID;
799    }
800  40 String name;
801  40 NameProvider np = getNameProvider();
802  40 if (np instanceof DefaultNameProvider) {
803  39 name = ((DefaultNameProvider) np).getName(locale, iID, nameKey, isStandardOffset(instant));
804    } else {
805  1 name = np.getName(locale, iID, nameKey);
806    }
807  40 if (name != null) {
808  39 return name;
809    }
810  1 return printOffset(getOffset(instant));
811    }
812   
813    /**
814    * Gets the millisecond offset to add to UTC to get local time.
815    *
816    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the offset for
817    * @return the millisecond offset to add to UTC to get local time
818    */
819    public abstract int getOffset(long instant);
820   
821    /**
822    * Gets the millisecond offset to add to UTC to get local time.
823    *
824    * @param instant instant to get the offset for, null means now
825    * @return the millisecond offset to add to UTC to get local time
826    */
 
827  6 toggle public final int getOffset(ReadableInstant instant) {
828  6 if (instant == null) {
829  2 return getOffset(DateTimeUtils.currentTimeMillis());
830    }
831  4 return getOffset(instant.getMillis());
832    }
833   
834    /**
835    * Gets the standard millisecond offset to add to UTC to get local time,
836    * when standard time is in effect.
837    *
838    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the offset for
839    * @return the millisecond offset to add to UTC to get local time
840    */
841    public abstract int getStandardOffset(long instant);
842   
843    /**
844    * Checks whether, at a particular instant, the offset is standard or not.
845    * <p>
846    * This method can be used to determine whether Summer Time (DST) applies.
847    * As a general rule, if the offset at the specified instant is standard,
848    * then either Winter time applies, or there is no Summer Time. If the
849    * instant is not standard, then Summer Time applies.
850    * <p>
851    * The implementation of the method is simply whether {@link #getOffset(long)}
852    * equals {@link #getStandardOffset(long)} at the specified instant.
853    *
854    * @param instant milliseconds from 1970-01-01T00:00:00Z to get the offset for
855    * @return true if the offset at the given instant is the standard offset
856    * @since 1.5
857    */
 
858  83 toggle public boolean isStandardOffset(long instant) {
859  83 return getOffset(instant) == getStandardOffset(instant);
860    }
861   
862    /**
863    * Gets the millisecond offset to subtract from local time to get UTC time.
864    * This offset can be used to undo adding the offset obtained by getOffset.
865    *
866    * <pre>
867    * millisLocal == millisUTC + getOffset(millisUTC)
868    * millisUTC == millisLocal - getOffsetFromLocal(millisLocal)
869    * </pre>
870    *
871    * NOTE: After calculating millisLocal, some error may be introduced. At
872    * offset transitions (due to DST or other historical changes), ranges of
873    * local times may map to different UTC times.
874    * <p>
875    * For overlaps (where the local time is ambiguous), this method returns the
876    * offset applicable before the gap. The effect of this is that any instant
877    * calculated using the offset from an overlap will be in "summer" time.
878    * <p>
879    * For gaps, this method returns the offset applicable before the gap, ie "winter" offset.
880    * However, the effect of this is that any instant calculated using the offset
881    * from a gap will be after the gap, in "summer" time.
882    * <p>
883    * For example, consider a zone with a gap from 01:00 to 01:59:<br />
884    * Input: 00:00 (before gap) Output: Offset applicable before gap DateTime: 00:00<br />
885    * Input: 00:30 (before gap) Output: Offset applicable before gap DateTime: 00:30<br />
886    * Input: 01:00 (in gap) Output: Offset applicable before gap DateTime: 02:00<br />
887    * Input: 01:30 (in gap) Output: Offset applicable before gap DateTime: 02:30<br />
888    * Input: 02:00 (after gap) Output: Offset applicable after gap DateTime: 02:00<br />
889    * Input: 02:30 (after gap) Output: Offset applicable after gap DateTime: 02:30<br />
890    * <p>
891    * NOTE: Prior to v2.0, the DST overlap behaviour was not defined and varied by hemisphere.
892    * Prior to v1.5, the DST gap behaviour was also not defined.
893    * In v2.4, the documentation was clarified again.
894    *
895    * @param instantLocal the millisecond instant, relative to this time zone, to get the offset for
896    * @return the millisecond offset to subtract from local time to get UTC time
897    */
 
898  16736 toggle public int getOffsetFromLocal(long instantLocal) {
899    // get the offset at instantLocal (first estimate)
900  16736 final int offsetLocal = getOffset(instantLocal);
901    // adjust instantLocal using the estimate and recalc the offset
902  16736 final long instantAdjusted = instantLocal - offsetLocal;
903  16736 final int offsetAdjusted = getOffset(instantAdjusted);
904    // if the offsets differ, we must be near a DST boundary
905  16736 if (offsetLocal != offsetAdjusted) {
906    // we need to ensure that time is always after the DST gap
907    // this happens naturally for positive offsets, but not for negative
908  98 if ((offsetLocal - offsetAdjusted) < 0) {
909    // if we just return offsetAdjusted then the time is pushed
910    // back before the transition, whereas it should be
911    // on or after the transition
912  49 long nextLocal = nextTransition(instantAdjusted);
913  49 if (nextLocal == (instantLocal - offsetLocal)) {
914  0 nextLocal = Long.MAX_VALUE;
915    }
916  49 long nextAdjusted = nextTransition(instantLocal - offsetAdjusted);
917  49 if (nextAdjusted == (instantLocal - offsetAdjusted)) {
918  0 nextAdjusted = Long.MAX_VALUE;
919    }
920  49 if (nextLocal != nextAdjusted) {
921  17 return offsetLocal;
922    }
923    }
924  16638 } else if (offsetLocal >= 0) {
925  1780 long prev = previousTransition(instantAdjusted);
926  1780 if (prev < instantAdjusted) {
927  1780 int offsetPrev = getOffset(prev);
928  1780 int diff = offsetPrev - offsetLocal;
929  1780 if (instantAdjusted - prev <= diff) {
930  104 return offsetPrev;
931    }
932    }
933    }
934  16615 return offsetAdjusted;
935    }
936   
937    /**
938    * Converts a standard UTC instant to a local instant with the same
939    * local time. This conversion is used before performing a calculation
940    * so that the calculation can be done using a simple local zone.
941    *
942    * @param instantUTC the UTC instant to convert to local
943    * @return the local instant with the same local time
944    * @throws ArithmeticException if the result overflows a long
945    * @since 1.5
946    */
 
947  153907 toggle public long convertUTCToLocal(long instantUTC) {
948  153907 int offset = getOffset(instantUTC);
949  153907 long instantLocal = instantUTC + offset;
950    // If there is a sign change, but the two values have the same sign...
951  153907 if ((instantUTC ^ instantLocal) < 0 && (instantUTC ^ offset) >= 0) {
952  0 throw new ArithmeticException("Adding time zone offset caused overflow");
953    }
954  153907 return instantLocal;
955    }
956   
957    /**
958    * Converts a local instant to a standard UTC instant with the same
959    * local time attempting to use the same offset as the original.
960    * <p>
961    * This conversion is used after performing a calculation
962    * where the calculation was done using a simple local zone.
963    * Whenever possible, the same offset as the original offset will be used.
964    * This is most significant during a daylight savings overlap.
965    *
966    * @param instantLocal the local instant to convert to UTC
967    * @param strict whether the conversion should reject non-existent local times
968    * @param originalInstantUTC the original instant that the calculation is based on
969    * @return the UTC instant with the same local time,
970    * @throws ArithmeticException if the result overflows a long
971    * @throws IllegalArgumentException if the zone has no equivalent local time
972    * @since 2.0
973    */
 
974  45131 toggle public long convertLocalToUTC(long instantLocal, boolean strict, long originalInstantUTC) {
975  45131 int offsetOriginal = getOffset(originalInstantUTC);
976  45131 long instantUTC = instantLocal - offsetOriginal;
977  45131 int offsetLocalFromOriginal = getOffset(instantUTC);
978  45131 if (offsetLocalFromOriginal == offsetOriginal) {
979  44885 return instantUTC;
980    }
981  246 return convertLocalToUTC(instantLocal, strict);
982    }
983   
984    /**
985    * Converts a local instant to a standard UTC instant with the same
986    * local time. This conversion is used after performing a calculation
987    * where the calculation was done using a simple local zone.
988    *
989    * @param instantLocal the local instant to convert to UTC
990    * @param strict whether the conversion should reject non-existent local times
991    * @return the UTC instant with the same local time,
992    * @throws ArithmeticException if the result overflows a long
993    * @throws IllegalInstantException if the zone has no equivalent local time
994    * @since 1.5
995    */
 
996  270 toggle public long convertLocalToUTC(long instantLocal, boolean strict) {
997    // get the offset at instantLocal (first estimate)
998  270 int offsetLocal = getOffset(instantLocal);
999    // adjust instantLocal using the estimate and recalc the offset
1000  270 int offset = getOffset(instantLocal - offsetLocal);
1001    // if the offsets differ, we must be near a DST boundary
1002  270 if (offsetLocal != offset) {
1003    // if strict then always check if in DST gap
1004    // otherwise only check if zone in Western hemisphere (as the
1005    // value of offset is already correct for Eastern hemisphere)
1006  34 if (strict || offsetLocal < 0) {
1007    // determine if we are in the DST gap
1008  14 long nextLocal = nextTransition(instantLocal - offsetLocal);
1009  14 if (nextLocal == (instantLocal - offsetLocal)) {
1010  0 nextLocal = Long.MAX_VALUE;
1011    }
1012  14 long nextAdjusted = nextTransition(instantLocal - offset);
1013  14 if (nextAdjusted == (instantLocal - offset)) {
1014  0 nextAdjusted = Long.MAX_VALUE;
1015    }
1016  14 if (nextLocal != nextAdjusted) {
1017    // yes we are in the DST gap
1018  13 if (strict) {
1019    // DST gap is not acceptable
1020  0 throw new IllegalInstantException(instantLocal, getID());
1021    } else {
1022    // DST gap is acceptable, but for the Western hemisphere
1023    // the offset is wrong and will result in local times
1024    // before the cutover so use the offsetLocal instead
1025  13 offset = offsetLocal;
1026    }
1027    }
1028    }
1029    }
1030    // check for overflow
1031  270 long instantUTC = instantLocal - offset;
1032    // If there is a sign change, but the two values have different signs...
1033  270 if ((instantLocal ^ instantUTC) < 0 && (instantLocal ^ offset) < 0) {
1034  0 throw new ArithmeticException("Subtracting time zone offset caused overflow");
1035    }
1036  270 return instantUTC;
1037    }
1038   
1039    /**
1040    * Gets the millisecond instant in another zone keeping the same local time.
1041    * <p>
1042    * The conversion is performed by converting the specified UTC millis to local
1043    * millis in this zone, then converting back to UTC millis in the new zone.
1044    *
1045    * @param newZone the new zone, null means default
1046    * @param oldInstant the UTC millisecond instant to convert
1047    * @return the UTC millisecond instant with the same local time in the new zone
1048    */
 
1049  30617 toggle public long getMillisKeepLocal(DateTimeZone newZone, long oldInstant) {
1050  30617 if (newZone == null) {
1051  1 newZone = DateTimeZone.getDefault();
1052    }
1053  30617 if (newZone == this) {
1054  30376 return oldInstant;
1055    }
1056  241 long instantLocal = convertUTCToLocal(oldInstant);
1057  241 return newZone.convertLocalToUTC(instantLocal, false, oldInstant);
1058    }
1059   
1060    // //-----------------------------------------------------------------------
1061    // /**
1062    // * Checks if the given {@link LocalDateTime} is within an overlap.
1063    // * <p>
1064    // * When switching from Daylight Savings Time to standard time there is
1065    // * typically an overlap where the same clock hour occurs twice. This
1066    // * method identifies whether the local datetime refers to such an overlap.
1067    // *
1068    // * @param localDateTime the time to check, not null
1069    // * @return true if the given datetime refers to an overlap
1070    // */
1071    // public boolean isLocalDateTimeOverlap(LocalDateTime localDateTime) {
1072    // if (isFixed()) {
1073    // return false;
1074    // }
1075    // long instantLocal = localDateTime.toDateTime(DateTimeZone.UTC).getMillis();
1076    // // get the offset at instantLocal (first estimate)
1077    // int offsetLocal = getOffset(instantLocal);
1078    // // adjust instantLocal using the estimate and recalc the offset
1079    // int offset = getOffset(instantLocal - offsetLocal);
1080    // // if the offsets differ, we must be near a DST boundary
1081    // if (offsetLocal != offset) {
1082    // long nextLocal = nextTransition(instantLocal - offsetLocal);
1083    // long nextAdjusted = nextTransition(instantLocal - offset);
1084    // if (nextLocal != nextAdjusted) {
1085    // // in DST gap
1086    // return false;
1087    // }
1088    // long diff = Math.abs(offset - offsetLocal);
1089    // DateTime dateTime = localDateTime.toDateTime(this);
1090    // DateTime adjusted = dateTime.plus(diff);
1091    // if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1092    // dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1093    // dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1094    // return true;
1095    // }
1096    // adjusted = dateTime.minus(diff);
1097    // if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1098    // dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1099    // dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1100    // return true;
1101    // }
1102    // return false;
1103    // }
1104    // return false;
1105    // }
1106    //
1107    //
1108    // DateTime dateTime = null;
1109    // try {
1110    // dateTime = localDateTime.toDateTime(this);
1111    // } catch (IllegalArgumentException ex) {
1112    // return false; // it is a gap, not an overlap
1113    // }
1114    // long offset1 = Math.abs(getOffset(dateTime.getMillis() + 1) - getStandardOffset(dateTime.getMillis() + 1));
1115    // long offset2 = Math.abs(getOffset(dateTime.getMillis() - 1) - getStandardOffset(dateTime.getMillis() - 1));
1116    // long offset = Math.max(offset1, offset2);
1117    // if (offset == 0) {
1118    // return false;
1119    // }
1120    // DateTime adjusted = dateTime.plus(offset);
1121    // if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1122    // dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1123    // dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1124    // return true;
1125    // }
1126    // adjusted = dateTime.minus(offset);
1127    // if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1128    // dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1129    // dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1130    // return true;
1131    // }
1132    // return false;
1133   
1134    // long millis = dateTime.getMillis();
1135    // long nextTransition = nextTransition(millis);
1136    // long previousTransition = previousTransition(millis);
1137    // long deltaToPreviousTransition = millis - previousTransition;
1138    // long deltaToNextTransition = nextTransition - millis;
1139    // if (deltaToNextTransition < deltaToPreviousTransition) {
1140    // int offset = getOffset(nextTransition);
1141    // int standardOffset = getStandardOffset(nextTransition);
1142    // if (Math.abs(offset - standardOffset) >= deltaToNextTransition) {
1143    // return true;
1144    // }
1145    // } else {
1146    // int offset = getOffset(previousTransition);
1147    // int standardOffset = getStandardOffset(previousTransition);
1148    // if (Math.abs(offset - standardOffset) >= deltaToPreviousTransition) {
1149    // return true;
1150    // }
1151    // }
1152    // return false;
1153    // }
1154   
1155    /**
1156    * Checks if the given {@link LocalDateTime} is within a gap.
1157    * <p>
1158    * When switching from standard time to Daylight Savings Time there is
1159    * typically a gap where a clock hour is missing. This method identifies
1160    * whether the local datetime refers to such a gap.
1161    *
1162    * @param localDateTime the time to check, not null
1163    * @return true if the given datetime refers to a gap
1164    * @since 1.6
1165    */
 
1166  22 toggle public boolean isLocalDateTimeGap(LocalDateTime localDateTime) {
1167  22 if (isFixed()) {
1168  0 return false;
1169    }
1170  22 try {
1171  22 localDateTime.toDateTime(this);
1172  16 return false;
1173    } catch (IllegalInstantException ex) {
1174  6 return true;
1175    }
1176    }
1177   
1178    /**
1179    * Adjusts the offset to be the earlier or later one during an overlap.
1180    *
1181    * @param instant the instant to adjust
1182    * @param earlierOrLater false for earlier, true for later
1183    * @return the adjusted instant millis
1184    */
 
1185  26 toggle public long adjustOffset(long instant, boolean earlierOrLater) {
1186    // a bit messy, but will work in all non-pathological cases
1187   
1188    // evaluate 3 hours before and after to work out if anything is happening
1189  26 long instantBefore = instant - 3 * DateTimeConstants.MILLIS_PER_HOUR;
1190  26 long instantAfter = instant + 3 * DateTimeConstants.MILLIS_PER_HOUR;
1191  26 long offsetBefore = getOffset(instantBefore);
1192  26 long offsetAfter = getOffset(instantAfter);
1193  26 if (offsetBefore <= offsetAfter) {
1194  6 return instant; // not an overlap (less than is a gap, equal is normal case)
1195    }
1196   
1197    // work out range of instants that have duplicate local times
1198  20 long diff = offsetBefore - offsetAfter;
1199  20 long transition = nextTransition(instantBefore);
1200  20 long overlapStart = transition - diff;
1201  20 long overlapEnd = transition + diff;
1202  20 if (instant < overlapStart || instant >= overlapEnd) {
1203  4 return instant; // not an overlap
1204    }
1205   
1206    // calculate result
1207  16 long afterStart = instant - overlapStart;
1208  16 if (afterStart >= diff) {
1209    // currently in later offset
1210  4 return earlierOrLater ? instant : instant - diff;
1211    } else {
1212    // currently in earlier offset
1213  12 return earlierOrLater ? instant + diff : instant;
1214    }
1215    }
1216    // System.out.println(new DateTime(transitionStart, DateTimeZone.UTC) + " " + new DateTime(transitionStart, this));
1217   
1218    //-----------------------------------------------------------------------
1219    /**
1220    * Returns true if this time zone has no transitions.
1221    *
1222    * @return true if no transitions
1223    */
1224    public abstract boolean isFixed();
1225   
1226    /**
1227    * Advances the given instant to where the time zone offset or name changes.
1228    * If the instant returned is exactly the same as passed in, then
1229    * no changes occur after the given instant.
1230    *
1231    * @param instant milliseconds from 1970-01-01T00:00:00Z
1232    * @return milliseconds from 1970-01-01T00:00:00Z
1233    */
1234    public abstract long nextTransition(long instant);
1235   
1236    /**
1237    * Retreats the given instant to where the time zone offset or name changes.
1238    * If the instant returned is exactly the same as passed in, then
1239    * no changes occur before the given instant.
1240    *
1241    * @param instant milliseconds from 1970-01-01T00:00:00Z
1242    * @return milliseconds from 1970-01-01T00:00:00Z
1243    */
1244    public abstract long previousTransition(long instant);
1245   
1246    // Basic methods
1247    //--------------------------------------------------------------------
1248   
1249    /**
1250    * Get the datetime zone as a {@link java.util.TimeZone}.
1251    *
1252    * @return the closest matching TimeZone object
1253    */
 
1254  392 toggle public java.util.TimeZone toTimeZone() {
1255  392 return java.util.TimeZone.getTimeZone(iID);
1256    }
1257   
1258    /**
1259    * Compare this datetime zone with another.
1260    *
1261    * @param object the object to compare with
1262    * @return true if equal, based on the ID and all internal rules
1263    */
1264    public abstract boolean equals(Object object);
1265   
1266    /**
1267    * Gets a hash code compatible with equals.
1268    *
1269    * @return suitable hashcode
1270    */
 
1271  6747 toggle public int hashCode() {
1272  6747 return 57 + getID().hashCode();
1273    }
1274   
1275    /**
1276    * Gets the datetime zone as a string, which is simply its ID.
1277    * @return the id of the zone
1278    */
 
1279  6 toggle public String toString() {
1280  6 return getID();
1281    }
1282   
1283    /**
1284    * By default, when DateTimeZones are serialized, only a "stub" object
1285    * referring to the id is written out. When the stub is read in, it
1286    * replaces itself with a DateTimeZone object.
1287    * @return a stub object to go in the stream
1288    */
 
1289  28 toggle protected Object writeReplace() throws ObjectStreamException {
1290  28 return new Stub(iID);
1291    }
1292   
1293    /**
1294    * Used to serialize DateTimeZones by id.
1295    */
 
1296    private static final class Stub implements Serializable {
1297    /** Serialization lock. */
1298    private static final long serialVersionUID = -6471952376487863581L;
1299    /** The ID of the zone. */
1300    private transient String iID;
1301   
1302    /**
1303    * Constructor.
1304    * @param id the id of the zone
1305    */
 
1306  28 toggle Stub(String id) {
1307  28 iID = id;
1308    }
1309   
 
1310  28 toggle private void writeObject(ObjectOutputStream out) throws IOException {
1311  28 out.writeUTF(iID);
1312    }
1313   
 
1314  48 toggle private void readObject(ObjectInputStream in) throws IOException {
1315  48 iID = in.readUTF();
1316    }
1317   
 
1318  48 toggle private Object readResolve() throws ObjectStreamException {
1319  48 return forID(iID);
1320    }
1321    }
1322   
1323    //-------------------------------------------------------------------------
1324    /**
1325    * Lazy initialization to avoid a synchronization lock.
1326    */
 
1327    static final class LazyInit {
1328   
1329    /** Cache of old zone IDs to new zone IDs */
1330    static final Map<String, String> CONVERSION_MAP = buildMap();
1331    /** Time zone offset formatter. */
1332    static final DateTimeFormatter OFFSET_FORMATTER = buildFormatter();
1333   
 
1334  2 toggle private static DateTimeFormatter buildFormatter() {
1335    // Can't use a real chronology if called during class
1336    // initialization. Offset parser doesn't need it anyhow.
1337  2 Chronology chrono = new BaseChronology() {
1338    private static final long serialVersionUID = -3128740902654445468L;
 
1339  43 toggle public DateTimeZone getZone() {
1340  43 return null;
1341    }
 
1342  43 toggle public Chronology withUTC() {
1343  43 return this;
1344    }
 
1345  0 toggle public Chronology withZone(DateTimeZone zone) {
1346  0 return this;
1347    }
 
1348  0 toggle public String toString() {
1349  0 return getClass().getName();
1350    }
1351    };
1352  2 return new DateTimeFormatterBuilder()
1353    .appendTimeZoneOffset(null, true, 2, 4)
1354    .toFormatter()
1355    .withChronology(chrono);
1356    }
1357   
 
1358  2 toggle private static Map<String, String> buildMap() {
1359    // Backwards compatibility with TimeZone.
1360  2 Map<String, String> map = new HashMap<String, String>();
1361  2 map.put("GMT", "UTC");
1362  2 map.put("WET", "WET");
1363  2 map.put("CET", "CET");
1364  2 map.put("MET", "CET");
1365  2 map.put("ECT", "CET");
1366  2 map.put("EET", "EET");
1367  2 map.put("MIT", "Pacific/Apia");
1368  2 map.put("HST", "Pacific/Honolulu"); // JDK 1.1 compatible
1369  2 map.put("AST", "America/Anchorage");
1370  2 map.put("PST", "America/Los_Angeles");
1371  2 map.put("MST", "America/Denver"); // JDK 1.1 compatible
1372  2 map.put("PNT", "America/Phoenix");
1373  2 map.put("CST", "America/Chicago");
1374  2 map.put("EST", "America/New_York"); // JDK 1.1 compatible
1375  2 map.put("IET", "America/Indiana/Indianapolis");
1376  2 map.put("PRT", "America/Puerto_Rico");
1377  2 map.put("CNT", "America/St_Johns");
1378  2 map.put("AGT", "America/Argentina/Buenos_Aires");
1379  2 map.put("BET", "America/Sao_Paulo");
1380  2 map.put("ART", "Africa/Cairo");
1381  2 map.put("CAT", "Africa/Harare");
1382  2 map.put("EAT", "Africa/Addis_Ababa");
1383  2 map.put("NET", "Asia/Yerevan");
1384  2 map.put("PLT", "Asia/Karachi");
1385  2 map.put("IST", "Asia/Kolkata");
1386  2 map.put("BST", "Asia/Dhaka");
1387  2 map.put("VST", "Asia/Ho_Chi_Minh");
1388  2 map.put("CTT", "Asia/Shanghai");
1389  2 map.put("JST", "Asia/Tokyo");
1390  2 map.put("ACT", "Australia/Darwin");
1391  2 map.put("AET", "Australia/Sydney");
1392  2 map.put("SST", "Pacific/Guadalcanal");
1393  2 map.put("NST", "Pacific/Auckland");
1394  2 return Collections.unmodifiableMap(map);
1395    }
1396    }
1397   
1398    }